//文件操作：增加、删除、修改
package main

import (
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"gopkg.in/pg.v5"
)

const add_sql = "insert into files(uid,title,create_tm,create_ip) values(?,'not complete',?,?)"
const getfid_sql = "select fid from files where uid=? and create_tm=? and create_ip=? limit 1"

func (self *Site) AddFile(uid uint32, create_ip string) int32 {
	db1 := self.Db.Conn
	tm1 := time.Now()
	db1.Exec(add_sql, uid, tm1, create_ip)
	var fid int32
	_, err := db1.QueryOne(&fid, getfid_sql, uid, tm1, create_ip)
	if err == nil {
		return fid
	} else {
		log.Println(err)
		return -1
	}
}

const query_sql = "select pathname from files where fid=?"

func (self *Site) GetPath(db1 *pg.DB, fid uint32) string {
	var path1 []string
	_, err := db1.Query(&path1, query_sql, fid)
	if err == nil {
		return filepath.Join(self.DynBase, path1[0])
	} else {
		log.Println(err)
		return ""
	}
}

type TitlePath struct {
	Title           string
	Pathname        string
	Recommand_count int
	Discuss_count   int
}

const gettp_sql = "select title,pathname,recommand_count,discuss_count from files where fid=?"
const increase_rc_sql = "update files set read_count=read_count+1 where fid=?"

func (self *Site) GetTitlePath(fid int) *TitlePath {
	db1 := self.Db.Conn
	res := new(TitlePath)
	_, err := db1.QueryOne(res, gettp_sql, fid)
	if err == nil {
		db1.Exec(increase_rc_sql, fid)
		return res
	} else {
		log.Println(err)
		return nil
	}
}

var del_sqls = []string{"delete from files where fid=?",
	"delete from discusions where fid=?",
	"delete from images where fid=?"}

func (self *Site) DelFile(fid int) error {
	db1 := self.Db.Conn
	os.Remove(self.GetPath(db1, uint32(fid)))
	//log.Printf("Remove %v\n",self.GetPath(db1, uint32(fid)))
	imgs := self.GetDocImgs(fid)
	if imgs != nil {
		for _, p1 := range imgs {
			//log.Printf("Remove %v ,URL:%v\n",path.Join("www",p1),p1)
			os.Remove(path.Join("www", p1))
		}
	}
	var err error
	for _, sql1 := range del_sqls {
		_, err = db1.Exec(sql1, fid)
	}
	return err
}

const update_sql = "update files set bid=?, title=?,pathname=?, modify_tm=? , modity_ip=? where fid=?"

func (self *Site) UpdateFile(fid, bid uint32, title, cxt, ip string) error {
	db1 := self.Db.Conn
	fp, err := os.Create(self.GetPath(db1, fid))
	if err != nil {
		now1 := time.Now()
		yearmonth := fmt.Sprintf("%s/upfiles/%v%s", self.DynBase, now1.Year(), now1.Month())
		os.MkdirAll(yearmonth, os.ModePerm|os.ModeDir)
		fp, _ = ioutil.TempFile(yearmonth, strconv.Itoa(int(bid)))
	}
	defer fp.Close()
	fp.WriteString(cxt)

	pre := fmt.Sprintf("%s%c", filepath.Join(self.DynBase), filepath.Separator)
	rPath := strings.TrimPrefix(fp.Name(), pre)
	//log.Printf("TrimPre:\n%s\n%s\n", fp.Name(), pre)
	db1.Exec(update_sql, bid, title, rPath, time.Now(), ip, fid)
	return nil
}

const updatepub_sql = "update files set bid=?,title=?,pathname=?,modify_tm=?,modity_ip=?,publish=true where fid=?"

func (self *Site) UpdateFilePublish(fid, bid uint32, title, cxt, ip string) error {
	var db1 *pg.DB
	db1 = self.Db.Conn
	fp, err := os.Create(self.GetPath(db1, fid))
	if err != nil {
		now1 := time.Now()
		yearmonth := fmt.Sprintf("%s/upfiles/%v%v", self.DynBase, now1.Year(), now1.Month())
		os.MkdirAll(yearmonth, os.ModePerm|os.ModeDir)
		fp, _ = ioutil.TempFile(yearmonth, strconv.Itoa(int(bid)))
	}
	defer fp.Close()
	fp.WriteString(cxt)

	pre := fmt.Sprintf("%s%c", filepath.Join(self.DynBase), filepath.Separator)
	rPath := strings.TrimPrefix(fp.Name(), pre)
	//log.Printf("TrimPre:\n%s\n%s\n", fp.Name(), pre)
	db1.Exec(updatepub_sql, bid, title, rPath, time.Now(), ip, fid)
	return nil
}

type Block struct {
	Bid         uint32
	Name        string
	Description string
}

const getblocks_sql = "select bid,name,description from blocks order by bid"

func (self *Site) GetBlocks() []Block {
	self.bmutex.RLock()
	var length = len(self.BlockCache)
	var blks []Block = make([]Block, length)
	var keys []int = make([]int, length)
	var i int = 0
	for k := range self.BlockCache {
		keys[i] = int(k)
		i++
	}
	sort.Ints(keys)
	for i, k := range keys {
		blks[i] = self.BlockCache[uint32(k)]
	}
	self.bmutex.RUnlock()
	return blks
}

func (self *Site) MakeBlockCache() {
	self.bmutex.Lock()
	self.BlockCache = make(map[uint32]Block)
	var blks []Block
	db1 := self.Db.Conn
	_, err := db1.Query(&blks, getblocks_sql)
	if err == nil {
		for _, v := range blks {
			self.BlockCache[v.Bid] = v
		}
	} else {
		log.Println(err)
	}
	self.bmutex.Unlock()
}

func (self *Site) GetBlockName(bid uint32) string {
	self.bmutex.RLock()
	v, ok := self.BlockCache[bid]
	self.bmutex.RUnlock()
	if ok {
		return v.Name
	} else {
		return ""
	}
}

const addblock_sql = "insert into blocks(name,description) values(?,?)"

func (self *Site) AddBlock(name, desc string) error {
	db1 := self.Db.Conn
	_, err := db1.Exec(addblock_sql, name, desc)
	self.MakeBlockCache()
	return err
}

const renameblock_sql = "update blocks set name=? where bid=?"

func (self *Site) RenameBlock(bid uint32, name string) error {
	db1 := self.Db.Conn
	_, err := db1.Exec(renameblock_sql, name, bid)
	self.MakeBlockCache()
	return err
}

type FileList struct {
	Fid       uint32
	Title     string
	Bid       uint32
	Modify_tm time.Time
}

const filelist_sql = "select fid,title,bid,modify_tm from files where uid=? and publish=? order by modify_tm desc limit 20 offset ?"

func (self *Site) GetFilesByUID(uid, offset uint32, pub bool) []FileList {
	var flist []FileList
	db1 := self.Db.Conn
	_, err := db1.Query(&flist, filelist_sql, uid, pub, offset)
	if err != nil {
		log.Println(err)
		return nil
	} else {
		return flist
	}
}

func (self *Site) GetFileNumByUID(uid uint32, pub bool) int {
	var res1 int
	db1 := self.Db.Conn
	_, err := db1.QueryOne(&res1, "select count(fid) from files where uid=? and publish=?", uid, pub)
	if err != nil {
		log.Println(err)
		return 0
	}
	return res1
}

const files_bid_sql = "select fid,title,bid,modify_tm from files where bid=? and publish=true order by modify_tm desc limit ? offset ?"
const files_b0_sql = "select fid,title,bid,modify_tm from files where publish=true order by modify_tm desc limit ? offset ?"

func (self *Site) GetFilesByBID(bid, limit, offset uint32) []FileList {
	var flist []FileList
	db1 := self.Db.Conn
	if bid > 0 {
		_, err := db1.Query(&flist, files_bid_sql, bid, limit, offset)
		if err != nil {
			log.Println(err)
			return nil
		} else {
			return flist
		}
	} else {
		_, err := db1.Query(&flist, files_b0_sql, limit, offset)
		if err != nil {
			log.Println(err)
			return nil
		} else {
			return flist
		}
	}
}

const getfiles_sql = "select fid,title,bid,modify_tm from files where publish=true order by modify_tm desc limit ? offset ?"

func (self *Site) GetFiles(limit, offset uint32) []FileList {
	var flist []FileList
	db1 := self.Db.Conn
	_, err := db1.Query(&flist, getfiles_sql, limit, offset)
	if err != nil {
		log.Println(err)
		return nil
	} else {
		return flist
	}
}

const vsearch_sql = "select fid,title,bid,modify_tm from files where publish=true and lower(title) like lower(?) order by modify_tm desc limit ? offset ?"

func (self *Site) SearchFiles(k string, limit, offset int) []FileList {
	var flist []FileList
	db1 := self.Db.Conn
	_, err := db1.Query(&flist, vsearch_sql, KeyFmt(k), limit, offset)
	if err != nil {
		log.Println(err)
		return nil
	} else {
		return flist
	}
}

const vsearch_count_sql = "select count(fid) from files where publish=true and lower(title) like lower(?)"

func (self *Site) SearchCount(k string) int {
	var res1 int
	db1 := self.Db.Conn
	_, err := db1.QueryOne(&res1, vsearch_count_sql, KeyFmt(k))
	if err != nil {
		log.Println(err)
		return 0
	} else {
		return res1
	}
}

const asearch_sql = "select fid,title,bid,modify_tm from files where publish=true and uid=? and lower(title) like lower(?) order by modify_tm desc limit ? offset ?"

func (self *Site) SearchFilesWithUID(k string, uid int, limit, offset int) []FileList {
	var flist []FileList
	db1 := self.Db.Conn
	_, err := db1.Query(&flist, asearch_sql, uid, KeyFmt(k), limit, offset)
	if err != nil {
		log.Println(err)
		return nil
	} else {
		return flist
	}
}

const asearch_count_sql = "select count(fid) from files where publish=true and uid=? and lower(title) like lower(?)"

func (self *Site) SearchCountWithUID(k string, uid int) int {
	var res1 int
	db1 := self.Db.Conn
	_, err := db1.QueryOne(&res1, asearch_count_sql, uid, KeyFmt(k))
	if err != nil {
		log.Println(err)
		return 0
	} else {
		return res1
	}
}
func KeyFmt(k string) string {
	r, err := regexp.Compile("[\\s]+")
	if err != nil {
		log.Println("regexp error:", err)
		return "NULL"
	}
	var rs1 = r.ReplaceAllString(strings.TrimSpace(k), "%")
	return "%" + rs1 + "%"
}

type FileContent struct {
	Bid      uint32
	Title    string
	Pathname string
}
type FileReturn struct {
	Bid     uint32
	Title   string
	Content string
}

const getfile_sql = "select bid,title,pathname from files where fid=?"

func (self *Site) GetFileByFID(fid uint32) *FileReturn {
	var f1 FileContent
	db1 := self.Db.Conn
	r, err := db1.QueryOne(&f1, getfile_sql, fid)
	if err != nil {
		log.Println(err)
		return &FileReturn{1, "无标题", "无正文"}
	}
	if r.RowsReturned() < 1 {
		return &FileReturn{1, "无标题", "无正文"}
	} else {
		b1, err := ioutil.ReadFile(filepath.Join(self.DynBase, f1.Pathname))
		if err == nil {
			return &FileReturn{f1.Bid, f1.Title, string(b1)}
		} else {
			log.Println(err)
			return &FileReturn{f1.Bid, f1.Title, "无正文"}
		}
	}
}

func (self *Site) GetTitleByFID(fid int) string {
	var res1 string
	db1 := self.Db.Conn
	_, err := db1.QueryOne(&res1, "select title from files where fid=?", fid)
	if err != nil {
		log.Println(err)
	}
	return res1
}

const pubfile_sql = "update files set publish=true,modify_tm=NOW() where fid=? and uid=?"

func (self *Site) PublishFile(fid, uid uint32) error {
	db1 := self.Db.Conn
	_, err := db1.Exec(pubfile_sql, fid, uid)
	return err
}

const addimg_sql = "insert into images(fid,pathname,description) values(?,?,?)"

func (self *Site) AddImage(fid int, pathname, desc string) error {
	db1 := self.Db.Conn
	_, err := db1.Exec(addimg_sql, fid, pathname, desc)
	return err
}

type ImgInfo struct {
	Id          int
	Pathname    string
	Description string
}

const img_fid_sql = "select id,pathname,description from images where fid=?"

func (self *Site) GetImageByFID(fid int) []ImgInfo {
	var res1 []ImgInfo
	db1 := self.Db.Conn
	_, err := db1.Query(&res1, img_fid_sql, fid)
	if err == nil {
		return res1
	} else {
		log.Println(err)
		return nil
	}
}

type ImgDoc struct {
	Fid      uint32
	Pathname string
}

const imgdoc_sql = "select fid,pathname from images where id in (select max(id) from images group by fid order by fid desc limit 5)"

func (self *Site) GetImgDoc5() []ImgDoc {
	var res1 []ImgDoc
	db1 := self.Db.Conn
	_, err := db1.Query(&res1, imgdoc_sql)
	if err == nil {
		return res1
	} else {
		log.Println(err)
		return nil
	}
}

const docimgs_sql = "select pathname from images where fid=?"

func (self *Site) GetDocImgs(fid int) []string {
	var res1 []string
	db1 := self.Db.Conn
	_, err := db1.Query(&res1, docimgs_sql, fid)
	if err == nil {
		return res1
	} else {
		log.Println(err)
		return nil
	}
}

const count0_sql = "select count(fid) from files"
const countbid_sql = "select count(fid) from files where bid=?"

func (self *Site) GetCountByBID(bid int) int {
	var res int
	db1 := self.Db.Conn
	if bid == 0 {
		_, err := db1.QueryOne(&res, count0_sql)
		if err == nil {
			return res
		} else {
			log.Println(err)
			return 0
		}
	} else {
		_, err := db1.QueryOne(&res, countbid_sql, bid)
		if err == nil {
			return res
		} else {
			log.Println(err)
			return 0
		}
	}
}

func (self *Site) RecommandDoc(fid int) {
	db1 := self.Db.Conn
	db1.Exec("update files set recommand_count=recommand_count+1 where fid=?", fid)
}

const savedis_sql = "insert into discusions(fid,discus,uid,showname,create_tm,create_ip) values(?,?,?,?,NOW(),?)"

func (self *Site) SaveDiscus(fid, uid int, showname, discus, ip string) {
	db1 := self.Db.Conn
	_, err := db1.Exec(savedis_sql, fid, discus, uid, showname, ip)
	if err == nil {
		db1.Exec("update files set discuss_count=discuss_count+1 where fid=?", fid)
	}
}

type DiscusItem struct {
	Discus    string
	Showname  string
	Create_tm time.Time
	Create_ip string
}

const getdiscus_sql = "select discus,showname,create_tm,create_ip from discusions where fid=? order by did desc limit ? offset ?"

func (self *Site) GetDiscuss(fid, limit, offset int) []DiscusItem {
	var res1 []DiscusItem
	db1 := self.Db.Conn
	_, err := db1.Query(&res1, getdiscus_sql, fid, limit, offset)
	if err == nil {
		return res1
	} else {
		log.Println(err)
		return nil
	}
}
func (self *Site) GetDiscusCount(fid int) int {
	var res1 int
	db1 := self.Db.Conn
	_, err := db1.Query(&res1, "select count(did) from discusions where fid=?", fid)
	if err == nil {
		return res1
	} else {
		log.Println(err)
		return 0
	}
}

const recommandtop_sql = "select fid,title,bid,modify_tm from files where publish=true order by recommand_count desc limit ?"

func (self *Site) GetRecommandTopN(n int) []FileList {
	var res1 []FileList
	db1 := self.Db.Conn
	_, err := db1.Query(&res1, recommandtop_sql, n)
	if err != nil {
		log.Println(err)
		return nil
	} else {
		return res1
	}
}

const readtop_sql = "select fid,title,bid,modify_tm from files where publish=true order by read_count desc limit ?"

func (self *Site) GetReadTopN(n int) []FileList {
	var res1 []FileList
	db1 := self.Db.Conn
	_, err := db1.Query(&res1, readtop_sql, n)
	if err != nil {
		log.Println(err)
		return nil
	} else {
		return res1
	}
}

const discustop_sql = "select fid,title,bid,modify_tm from files where publish=true order by discuss_count desc limit ?"

func (self *Site) GetDiscusTopN(n int) []FileList {
	var res1 []FileList
	db1 := self.Db.Conn
	_, err := db1.Query(&res1, discustop_sql, n)
	if err != nil {
		log.Println(err)
		return nil
	} else {
		return res1
	}
}

func (self *Site) ExecTemplateWithFuncs(filename, tname string, wr io.Writer, data interface{}, func_map template.FuncMap) error {
	fn := filepath.Join(self.DynBase, filename)
	var t1 *template.Template
	if func_map != nil {
		t1, _ = template.New("").Funcs(func_map).Parse("")
	} else {
		t1, _ = template.New("").Parse("")
	}
	_, err := t1.ParseFiles(fn)
	if err != nil {
		return err
	}
	if len(tname) == 0 {
		for _, t := range t1.Templates() {
			err = t.Execute(wr, data)
			if err != nil {
				return err
			}
		}
	} else {
		err = t1.ExecuteTemplate(wr, tname, data)
		if err != nil {
			return err
		}
	}
	return nil
}
func (self *Site) OpenTemplateWithFuncs(filename string, func_map template.FuncMap) (*template.Template, error) {
	fn := filepath.Join(self.DynBase, filename)
	t1, _ := template.New("").Funcs(func_map).Parse("")
	_, err := t1.ParseFiles(fn)
	if err != nil {
		return nil, err
	}
	return t1, nil
}
